/*----------------------------------------------------------------------
 *
 * Copyright 2009, Thomas Dejanovic.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 *---------------------------------------------------------------------
 *
 * Serial port interface to provide a parallel interface.
 *
 * based on code on the Leon / Rachel SPARC project.
 *
 *---------------------------------------------------------------------*/


module dcom_uart

  (/*AUTOARG*/
  // Outputs
  rxd_out, rx_data, rx_data_valid, rx_error, tx_ready,
  // Inputs
  clk, clk_en, reset, txd_in, dcom_scaler, tx_data, tx_data_valid
  );

  //----------------------------------------
  input                 clk;
  input                 clk_en;

  input                 reset;
  //----------------------------------------

  //----------------------------------------
  // serial port interface.
  input                 txd_in;
  output                rxd_out;

  // scaler value is supplied externally
  // values are clock rate / (8 x baud rate) - 1, so for:
  // 115200 @ 100MHz = 108
  //  57600 @ 100MHz = 216
  // 115200 @  90MHz =  97
  //  57600 @  90MHz = 194
  // 115200 @  50MHz =  53
  //  57600 @  50MHz = 108
  input [7:0]           dcom_scaler;
  //----------------------------------------

  //----------------------------------------
  // data from the host
  output [7:0]          rx_data;
  output                rx_data_valid;
  output                rx_error;

  // data to be sent to the host.
  input [7:0]           tx_data;
  input                 tx_data_valid;
  output                tx_ready;
  //----------------------------------------


  /*----------------------------------------------------------------*/

  /*-AUTOUNUSED-*/

  /*AUTOINPUT*/

  /*AUTOOUTPUT*/

  /*-AUTOREGINPUT-*/

  /*AUTOREG*/

  /*AUTOWIRE*/

  /*------------------------------------------------------------------
   *
   * local definitions and connections.
   *
   * */

  /*-----------------------------------------------------------------
   *
   * Tick Generation
   *
   * preloading down counter.
   *
   *
   * */

  //

  reg [7:0]             tick_count;
  reg                   tick;
  always @ (posedge clk) if (clk_en) begin
    if (reset) begin
      tick_count <= 8'd0;
      tick       <= 1'd0;
    end else if (tick_count == 8'd0) begin
      tick_count <= dcom_scaler;
      tick       <= 1'd1;
    end else begin
      tick_count <= tick_count - 8'd1;
      tick       <= 1'd0;
    end
  end

  /*-----------------------------------------------------------------
   *
   * RX data debounce and sample.
   *
   * */

  reg [7:0]             rx_sample;
  reg                   rx_bit;
  reg                   rx_bit_d;
  always @ (posedge clk) if (clk_en) begin
    if (reset) begin
      rx_sample <= 8'd0;
      rx_bit    <= 1'd0;
      rx_bit_d  <= 1'd0;
    end else begin
      rx_sample <= {rx_sample[6:0], txd_in};
      if ((rx_sample == 8'h0) || (rx_sample == 8'hff))
        rx_bit   <= rx_sample[7];
      rx_bit_d <= rx_bit;
    end
  end

  wire rx_falling_edge = ~rx_bit & rx_bit_d;

  /*-----------------------------------------------------------------
   *
   * RX FSM.
   *
   * the rx phase count is reset when there is a falling edge of rx
   * data and the FSM is in idle.  Data is sampled at the end of
   * phase0 and the objective is to have edge transitions in phase5.
   *
   * */

  parameter RX_STATE_SIZE  = 2;

  parameter [1:0]
               RX_IDLE      = 2'd0,
               RX_DATA_BIT  = 2'd1,
               RX_START_BIT = 2'd2,
               RX_STOP_BIT  = 2'd3;


  reg [RX_STATE_SIZE-1:0] rx_state;

`ifdef IVERILOG
  reg [95:0]            _rx_stateascii;
  always @(rx_state) begin
    case ({rx_state})
      RX_IDLE:      _rx_stateascii = "rx_idle     ";
      RX_DATA_BIT:  _rx_stateascii = "rx_data_bit ";
      RX_START_BIT: _rx_stateascii = "rx_start_bit";
      RX_STOP_BIT:  _rx_stateascii = "rx_stop_bit ";
      default:      _rx_stateascii = "%Error      ";
    endcase
  end
`endif


  //
  // phase counter.
  //
  reg [2:0]             rx_phase;
  reg                   rx_tick;
  always @ (posedge clk) if (clk_en) begin
    if (reset) begin
      rx_phase <= 3'd0;
      rx_tick  <= 1'd0;
    end else if ((rx_state == RX_IDLE) && rx_falling_edge) begin
      rx_phase <= 3'd5;
      rx_tick  <= 1'd0;
    end else begin
      if (tick) rx_phase <= rx_phase + 3'd1;
      rx_tick  <= tick && (rx_phase == 3'd0);
    end
  end

  //
  // RX FSM.
  //

  reg       rx_error; // cleared by reception of 0x55
  reg [7:0] rx_data;
  reg       rx_data_valid;
  always @ (posedge clk) if (clk_en) begin
    if (reset) begin
      rx_state      <= RX_IDLE;
      rx_error      <= 1'd0;
      rx_data       <= 8'hff;
      rx_data_valid <= 1'd0;
    end else begin
      rx_data       <= rx_data;
      rx_data_valid <= 1'd0;
      rx_error      <= rx_error;
      case (rx_state)
        RX_IDLE: begin
          // wait for falling edge.
          if (rx_falling_edge)
            rx_state <= RX_START_BIT;
          // load the word with 1's so that we can detect the start
          // bit (a zero) as it gets shifted through the byte.
          rx_data <= 8'hff;
       end

        RX_START_BIT : begin
          // check that we sample a 0.
          if (rx_tick)
            if (rx_bit)
              rx_state <= RX_IDLE;
            else begin
              rx_state <= RX_DATA_BIT;
              rx_data  <= 8'h7f;
            end
        end

        RX_DATA_BIT : begin
          if (rx_tick) begin
            // if the last bit in the byte is 0, that is the start bit
            // we sampled to start with, so now go look for a stop bit.
            rx_data      <= {rx_bit, rx_data[7:1]};
            rx_state     <= rx_data[0] ? RX_DATA_BIT : RX_STOP_BIT;
          end
        end

        RX_STOP_BIT : begin
          if (rx_tick) begin
            rx_state  <= RX_IDLE;
            if (rx_bit) begin
              // we got a valid stop bit, clear any errors.
              rx_error      <= 1'd0;
              rx_data_valid <= 1'd1;
            end else begin
              // we did not get a stop bit.
              rx_error      <= 1'd1;
              rx_data_valid <= 1'd0;
              rx_data       <= 8'hff;
            end
          end
        end

        default : begin
          rx_state      <= RX_IDLE;
          rx_data       <= 8'hff;
          rx_data_valid <= 1'd0;
          rx_error      <= 1'd1;
        end
      endcase // case(rx_state)
    end // else: !if(reset)
  end // if (clk_en)

  /*-----------------------------------------------------------------
   *
   * Tx FSM.
   *
   * */

  //
  // phase counter (same as Rx but does not get adjusted).
  //
  reg [2:0]             tx_phase;
  reg                   tx_tick;
  always @ (posedge clk) if (clk_en) begin
    if (reset) begin
      tx_phase <= 3'd0;
      tx_tick  <= 1'd0;
    end else begin
      if (tick) tx_phase <= tx_phase + 3'd1;
      tx_tick  <= tick && (tx_phase == 3'd0);
    end
  end


  parameter TX_STATE_SIZE  = 2;

  parameter [1:0]
               TX_IDLE   = 2'd0,
               TX_DATA   = 2'd1;


  reg [TX_STATE_SIZE-1:0] tx_state;

  reg [95:0]            _tx_stateascii;
  always @(tx_state) begin
    case ({tx_state})
      TX_IDLE:      _tx_stateascii = "tx_idle     ";
      TX_DATA:      _tx_stateascii = "tx_data     ";
      default:      _tx_stateascii = "%Error      ";
    endcase
  end

  reg [10:0] tx_data_shift;
  reg        tx_ready;
  always @ (posedge clk) if (clk_en) begin
    if (reset) begin
      tx_state      <= TX_IDLE;
      tx_data_shift <= 10'h3ff;
      tx_ready      <= 1'd0;
    end else begin
      case (tx_state)
        TX_IDLE : begin
          tx_state      <= tx_data_valid ? TX_DATA : TX_IDLE;
          tx_data_shift <= {1'd1, tx_data, 2'b01};
          tx_ready      <= ~tx_data_valid;
        end

        TX_DATA : begin
          if (tx_tick) begin
            if (tx_data_shift[10:1] == 9'h001) begin
              tx_state <= TX_IDLE;
              tx_ready <= 1'd1;
            end else begin
              tx_state <= TX_DATA;
              tx_ready <= 1'd0;
            end
            tx_data_shift <= {1'd0, tx_data_shift[10:1]};
          end
        end

        default : begin
          tx_state      <= TX_IDLE;
          tx_data_shift <= 10'h3ff;
          tx_ready      <= 1'd0;
        end
      endcase // case(tx_state)
    end
  end

  wire rxd_out = tx_data_shift[0];

  /*----------------------------------------------------------------*/

endmodule // dcom_uart


// Local Variables:
// verilog-library-directories:(".")
// verilog-library-extensions:(".v")
// End:
